﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Common.Utilities.TextMatch;

namespace Workflows.Components.Entities.Distances.FieldDistances
{
    /// <summary>
    /// stop word util + cosine distance
    /// </summary>
    public class DocumentComparer:IAttributeDistanceCalcutor
    {
        private StopWordsUtil _StopWordsUtil;
        private Regex _Splitter;
        private bool _Initialized = false;

        #region Implementation of IAttributeComparer

        /// <summary>
        /// load dictionary into memory or read config settings from file
        /// </summary>
        public void InitInMemorySettings()
        {
            this._Splitter = new Regex("([ \\t{}():;. \n])");
            this._StopWordsUtil=new StopWordsUtil(StopWordsUtil.StopWordList.PubmedStopWordList);
            this._Initialized = true;
        }

        /// <summary>
        /// return similarity score in [0,1]
        /// 0 indicate no similarity or null match
        /// 1 indicate perfect match
        /// </summary>
        /// <param name="attrValue1"></param>
        /// <param name="attrValue2"></param>
        /// <param name="result"></param>
        /// <returns>in the range of [0,1]</returns>
        public double Compare(object attrValue1, object attrValue2, ref MatchResult result)
        {
            if (!(attrValue1 is List<TermFreq>) || !(attrValue2 is List<TermFreq>))
            {
                throw new ArgumentException("Document comparer requires argument to be of List<TermFreq> type");
            }

            if(attrValue1==null || attrValue2==null)
            {
                result = MatchResult.NullMatch;
                return 0;
            }

            if(!this._Initialized)
            {
                this.InitInMemorySettings();
            }

            //Dictionary<string, int> termFreqs1 = this.Split(attrValue1.ToString());
            //Dictionary<string, int> termFreqs2 = this.Split(attrValue2.ToString());
            List<TermFreq> termFreqs1 = attrValue1 as List<TermFreq>;
            List<TermFreq> termFreqs2 = attrValue2 as List<TermFreq>;
            double? distance = this.CalculateDistance(termFreqs1, termFreqs2);
            if(distance==null)
            {
                result = MatchResult.NullMatch;
                return 0;
            }
            else if(distance.Value==0)
                result = MatchResult.NegativeMatch;
            else
                result = MatchResult.PositiveMatch;
            double similarity = distance.Value;
            similarity = Math.Max(0, Math.Min(1, similarity));
            return similarity;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity1"></param>
        /// <param name="entity2"></param>
        /// <param name="attrMetaData"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public double Compare(Entity entity1, Entity entity2, 
            AttributeMetaData attrMetaData, ref MatchResult result)
        {
            Debug.Assert(!string.IsNullOrEmpty(attrMetaData.AttributeName));

            string attrName = attrMetaData.AttributeName;
            object attrValue1 = null;
            object attrValue2 = null;
            if (entity1.ComparableAttributes.ContainsKey(attrName))
                attrValue1 = entity1.ComparableAttributes[attrName];
            if (entity2.ComparableAttributes.ContainsKey(attrName))
                attrValue2 = entity2.ComparableAttributes[attrName];
            return this.Compare(attrValue1, attrValue2, ref result);
        }

        /// <summary>
        /// argument to bind attribute name
        /// </summary>
        public string[] ArgumentNames
        {
            get
            {
                throw new System.NotImplementedException();
            }
        }

        public Dictionary<string, string> ComparerSettings
        {
            get { return new Dictionary<string, string>(); }
            set { }
        }

        #endregion

        #region tokenize
        private Dictionary<string,int> Split(string content)
        {
            if(string.IsNullOrEmpty(content))
                return null;

            Dictionary<string,int> termFreqs=new Dictionary<string, int>();
            content = content.ToLower().Trim();
            string[] words = this._Splitter.Split(content);
            foreach(string word in words)
            {
                if(!this._StopWordsUtil.StopWords.Contains(word))
                {
                    if (termFreqs.ContainsKey(word))
                        termFreqs[word] = termFreqs[word] + 1;
                    else 
                        termFreqs.Add(word,1);
                }
            }
            return termFreqs;
        }
        #endregion

        #region cosine distance
        private double? CalculateDistance(
            List<TermFreq> termFreqs1,
            List<TermFreq> termFreqs2)
        {
            if (termFreqs1 == null || termFreqs2 == null ||
                termFreqs1.Count == 0 || termFreqs2.Count == 0)
                return null;

            Dictionary<string, int> unionTerms1 = new Dictionary<string, int>();
            Dictionary<string, int> unionTerms2 = new Dictionary<string, int>();
            foreach (TermFreq termFreq in termFreqs1)
            {
                unionTerms1.Add(termFreq.Term, termFreq.Freq);
            }
            foreach (TermFreq termFreq in termFreqs2)
            {
                unionTerms2.Add(termFreq.Term, termFreq.Freq);
                if (!unionTerms1.ContainsKey(termFreq.Term))
                    unionTerms1.Add(termFreq.Term, 0);
            }
            foreach (TermFreq termFreq in termFreqs1)
            {
                if (!unionTerms2.ContainsKey(termFreq.Term))
                    unionTerms2.Add(termFreq.Term, 0);
            }

            double length1 = 0;
            double length2 = 0;
            double svMul = 0;
            Dictionary<string, double> commonTerms = new Dictionary<string, double>();
            foreach (string term in unionTerms1.Keys)
            {
                double freq1 = unionTerms1[term];
                double freq2 = unionTerms2[term];
                length1 += Math.Pow(freq1, 2);
                length2 += Math.Pow(freq2, 2);
                svMul += freq2 * freq1;

                if (freq1 > 0 && freq2 > 0)
                {
                    int count = count = Math.Min(unionTerms1[term], unionTerms2[term]);
                    if (count > 0)
                        commonTerms.Add(term, count);
                }
            }

            length1 = Math.Sqrt(length1);
            length2 = Math.Sqrt(length2);

            double cosineDistance = svMul / (length1 * length2);
            //double docLenScale = (double)commonTerms.Count * 2 /
            //                     (termFreqs1.Count + termFreqs2.Count - commonTerms.Count);
            //cosineDistance = cosineDistance * docLenScale;

            return cosineDistance;
        }
        #endregion
    }
}
